package cn.xy.commons.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
import lombok.extern.slf4j.Slf4j;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Easy Excel 简单封装
 * <a href="https://easyexcel.opensource.alibaba.com/docs/current/">官方使用文档</a>
 *
 *  
 */
@Slf4j
public class ExcelUtil {

    private static final int SEPARATE_SIZE = 60000;

    private ExcelUtil() {
        throw new IllegalStateException("Utility class");
    }

    /**
     * 创建Excel文件流
     *
     * @param dateList  数据
     * @param sheetName sheet名
     * @param excelType Excel类型
     * @param separate  数据量过大时是否将数据拆分到多个sheet表中
     * @param clazz     Class
     * @param <T>       type
     * @return 文件流
     */
    public static <T> byte[] create(List<T> dateList, String sheetName, ExcelTypeEnum excelType, boolean separate, Class<T> clazz) {
        sheetName = CharSequenceUtil.isNotEmpty(sheetName) ? sheetName : "sheet";
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        if (separate && dateList.size() > SEPARATE_SIZE) {
            int size = 0;
            List<T> sheetList = new ArrayList<>();
            ExcelWriter excelWriter = EasyExcelFactory.write(outputStream).excelType(excelType).build();
            for (int i = 0; i < dateList.size(); i++) {
                sheetList.add(dateList.get(i));
                if (sheetList.size() == SEPARATE_SIZE || i == (dateList.size() - 1)) {
                    size++;
                    WriteSheet writeSheet = EasyExcelFactory.writerSheet(sheetName + size).head(clazz).build();
                    excelWriter.write(sheetList, writeSheet);
                    sheetList = new ArrayList<>();
                }
            }
            excelWriter.finish();
        } else {
            EasyExcelFactory.write(outputStream, clazz).excelType(excelType).sheet(sheetName).doWrite(dateList);
        }
        return outputStream.toByteArray();
    }

    /**
     * 创建Excel文件流（自定义表头）
     *
     * @param headList 列名
     * @param dateList 数据
     * @return 文件流
     */
    public static byte[] create(List<String> headList, List<List<String>> dateList) {
        return ExcelUtil.create(headList, dateList, null, ExcelTypeEnum.XLSX);
    }

    /**
     * 创建Excel文件流（自定义表头）
     *
     * @param headList  列名
     * @param dateList  数据
     * @param sheetName sheet名
     * @param excelType Excel类型
     * @return 文件流
     */
    public static byte[] create(List<String> headList, List<List<String>> dateList, String sheetName, ExcelTypeEnum excelType) {
        List<List<String>> headsList = new ArrayList<>();
        for (String head : headList) {
            headsList.add(CollUtil.toList(head));
        }
        sheetName = CharSequenceUtil.isNotEmpty(sheetName) ? sheetName : "sheet";
        excelType = excelType == null ? ExcelTypeEnum.XLSX : excelType;
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        EasyExcelFactory.write(outputStream).excelType(excelType).head(headsList).sheet(sheetName).doWrite(dateList);
        return outputStream.toByteArray();
    }

    /**
     * 创建Excel文件流
     *
     * @param dateList 数据
     * @param clazz    Class
     * @param <T>      type
     * @return 文件流
     */
    public static <T> byte[] create(List<T> dateList, Class<T> clazz) {
        return ExcelUtil.create(dateList, null, ExcelTypeEnum.XLSX, true, clazz);
    }

    /**
     * 读取Excel数据
     *
     * @param inputStream Excel文件流
     * @param sheetIndex  读取的sheet
     * @param clazz       Class
     * @param <T>         type
     * @return Excel内的数据
     */
    public static <T> List<T> read(InputStream inputStream, int sheetIndex, Class<T> clazz) {
        List<T> result = CollUtil.newArrayList();
        EasyExcelFactory.read(inputStream, clazz, new ReadListener<T>() {
            @Override
            public void invoke(T data, AnalysisContext context) {
                result.add(data);
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                log.info("excel 读取完成, 有效数据: {}条", result.size());
            }
        }).sheet(sheetIndex).doRead();
        return result;
    }

    /**
     * 读取Excel数据
     *
     * @param excelBytes Excel文件流
     * @param sheetName  读取的sheet名称
     * @param clazz      Class
     * @param <T>        type
     * @return Excel内的数据
     */
    public static <T> List<T> read(byte[] excelBytes, String sheetName, Class<T> clazz) {
        return ExcelUtil.read(IoUtil.toStream(excelBytes), sheetName, clazz);
    }

    /**
     * 读取Excel数据
     *
     * @param inputStream Excel文件流
     * @param sheetName   读取的sheet名称
     * @param clazz       Class
     * @param <T>         type
     * @return Excel内的数据
     */
    public static <T> List<T> read(InputStream inputStream, String sheetName, Class<T> clazz) {
        List<T> result = CollUtil.newArrayList();
        EasyExcelFactory.read(inputStream, clazz, new ReadListener<T>() {
            @Override
            public void invoke(T data, AnalysisContext context) {
                result.add(data);
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                log.info("excel 读取完成, 有效数据: {}条", result.size());
            }
        }).sheet(sheetName).doRead();
        return result;
    }

    /**
     * 读取Excel数据
     *
     * @param inputStream Excel文件流
     * @param clazz       Class
     * @param <T>         type
     * @return Excel内的数据
     */
    public static <T> List<T> read(InputStream inputStream, Class<T> clazz) {
        return ExcelUtil.read(inputStream, 0, clazz);
    }

    /**
     * 读取Excel数据
     *
     * @param excelBytes Excel文件流
     * @param sheetIndex 读取的sheet
     * @param clazz      Class
     * @param <T>        type
     * @return Excel内的数据
     */
    public static <T> List<T> read(byte[] excelBytes, int sheetIndex, Class<T> clazz) {
        return ExcelUtil.read(IoUtil.toStream(excelBytes), sheetIndex, clazz);
    }

    /**
     * 读取Excel数据
     *
     * @param excelBytes Excel文件流
     * @param clazz      Class
     * @param <T>        type
     * @return Excel内的数据
     */
    public static <T> List<T> read(byte[] excelBytes, Class<T> clazz) {
        return ExcelUtil.read(excelBytes, 0, clazz);
    }


    /**
     * 读取Excel数据
     *
     * @param excelBytes Excel文件流
     * @return Excel内的数据
     */
    public static List<Map<String, Object>> read(byte[] excelBytes) {
        return ExcelUtil.read(IoUtil.toStream(excelBytes), 0);
    }

    /**
     * 读取Excel数据
     *
     * @param inputStream Excel文件流
     * @return Excel内的数据
     */
    public static List<Map<String, Object>> read(InputStream inputStream) {
        return ExcelUtil.read(inputStream, 0);
    }

    /**
     * 读取Excel数据
     *
     * @param inputStream Excel文件流
     * @param sheetIndex  读取的sheet
     * @return Excel内的数据
     */
    public static List<Map<String, Object>> read(InputStream inputStream, int sheetIndex) {
        Map<Integer, String> headKeyMap = new HashMap<>();
        List<Map<String, Object>> result = CollUtil.newArrayList();
        EasyExcelFactory.read(inputStream, new AnalysisEventListener<Object>() {
            @Override
            public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
                // 读取表头
                for (Map.Entry<Integer, ReadCellData<?>> entry : headMap.entrySet()) {
                    if (CharSequenceUtil.isEmpty(entry.getValue().getStringValue())) {
                        continue;
                    }
                    headKeyMap.putIfAbsent(entry.getValue().getColumnIndex(), entry.getValue().getStringValue());
                }
            }

            @Override
            public void invoke(Object data, AnalysisContext context) {
                // 根据表头填充数据
                Map<Integer, Object> dataMap = (Map<Integer, Object>) data;
                if (dataMap == null) {
                    return;
                }
                Map<String, Object> currentMap = new HashMap<>();
                for (Map.Entry<Integer, Object> dataEntry : dataMap.entrySet()) {
                    String head = headKeyMap.get(dataEntry.getKey());
                    if (CharSequenceUtil.isEmpty(head)) {
                        continue;
                    }
                    currentMap.putIfAbsent(head, dataEntry.getValue());
                }
                result.add(currentMap);
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                log.info("excel 读取完成, 有效数据: {}条", result.size());
            }
        }).sheet(sheetIndex).doRead();
        return result;
    }

    /**
     * 生成返回消息
     *
     * @param total   读取到的总数据
     * @param success 导入成功
     * @param error   导入失败
     * @return message
     */
    public static String getMessage(int total, int success, int error) {
        return "成功读取到" + total + "条数据，其中导入成功" + success + "条数据，导入失败" + error + "条数据";
    }

}
